#include "shader.h"
#include <stdlib.h>
#include <stdio.h>
#include "Matrix4.h"
#include <math.h>   
#include <iostream> 
#include <sstream>
#include <vector>
#include <sys/time.h>
#include "FrustumMath.h"
#include "Angles.h"
#include "Texture.h"
#include "FPSCamera.h"
#include "diamond_square.h"
#include "objreader.h"

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

bool height_locked = false;
bool drawBear=true;

// Expressed as float so gluPerspective division returns a float and not 0 (640/480 != 640.0/480.0).
static double RENDER_WIDTH = 1280;
static double RENDER_HEIGHT = 800;

static int PROJECTION_X,PROJECTION_Z;

#define SHADOW_MAP_RATIO 2

GLuint iceTexture;
GLuint bearTexture;
GLuint gunTexture;
GLuint skyTexture[6];

// Use to activate/disable shadowShader
GLhandleARB shadowShaderId;
GLuint shadowMapUniform;

//GLfloat light0_position[] = { 10.0, 10.0, 0.0, 0.0 };
//Light position
float p_light[3] = {3,60,0};
//Light lookAt
float l_light[3] = {0,0,-5};
// Hold id of the framebuffer for light POV rendering
GLuint fboId;
// Z values will be rendered to this texture when using fboId framebuffer
GLuint depthTextureId;
//Light mouvement circle radius
float light_mvnt = 30.0f;

static Shader *shader,*terrainshader,*bearshader,*gun_shader;
static Shader *skyshader;

static int shadowtoggle = 1;
static int shadowlight = 1;

using namespace std;

//variables for reading object
int nVerts;
float *vertices;
float *normals;
float *texcoords;
int nIndices;
int *indices;

static Matrix4 *identity; 
static FPSCamera *camera;

int width=700;
int height=700;

FrustumMath fmath;

void display(void);
void reshape(int, int);
void idle();
void setup_scene();
void keyboard();

int FOV = 60;
double width_nearplane;
double height_nearplane;

double width_farplane;
double height_farplane;

double max_height;

int nearplane=1;
int farplane=100000;

double aspect_ratio=1.0;

// (Power of two) plus 1
#define HMAP_SIZE 65

double **heightmap;

void initGL()
{
	glPointSize(5.0);
    
	glEnable(GL_TEXTURE_2D);   // enable texture mapping      
	glShadeModel(GL_SMOOTH);   // enable smooth shading 
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  // black background                                                                 
	glClearDepth(1.0f);        // depth buffer setup           
    
	glEnable (GL_LIGHTING);
	glEnable (GL_LIGHT0);
    
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
    
	glEnable(GL_DEPTH_TEST);   // enables depth testing                                   
	glDepthFunc(GL_LEQUAL);    // configure depth testing
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);          // really nice perspective calculations          
    
	// Select GL_MODULATE to mix texture with color for shading:
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
	int shadowMapWidth = RENDER_WIDTH * SHADOW_MAP_RATIO;
	int shadowMapHeight = RENDER_HEIGHT * SHADOW_MAP_RATIO;
	
	GLenum FBOstatus;
	
	// Try to use a texture depth component
	glGenTextures(1, &depthTextureId);
	glBindTexture(GL_TEXTURE_2D, depthTextureId);
	// Use bilinear filtering:
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	// Wrap texture over at the edges:
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    
	// No need to force GL_DEPTH_COMPONENT24, drivers usually give you the max precision if available 
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	// create a framebuffer object
	glGenFramebuffersEXT(1, &fboId);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
	// Instruct openGL that we won't bind a color texture with the currently binded FBO
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);
	
	// attach the texture to FBO depth attachment point
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, depthTextureId, 0);
	
	// check FBO status
	FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if (FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO: %s line %d\n", __FILE__, __LINE__);
	
	// switch back to window-system-provided framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    
	heightmap = generate_heightmap(HMAP_SIZE, 500.0);
	// Find max value so that we can create a color that will always be in the range 0 to 1.
	max_height = heightmap[0][0];
    
	for (int i=0; i<HMAP_SIZE; i++) {
		for (int j=0; j<HMAP_SIZE; j++) {
			max_height = max(max_height, heightmap[i][j]);
		}
	}
}

struct timeval current_time,last_time;
double seconds,useconds;
double hmap_scale = 5.0;
typedef Vector3 Vertex;

void glVertexV(Vertex v) {
	glVertex3f(v[0], v[1], v[2]);
}

void glNormalV(Vertex normal) {
	normal.normalize();
	glNormal3f(normal[0], normal[1], normal[2]);
}

Vertex vertexFromHeightMap(int i, int j) {
	double height = heightmap[i][j];
    
	return Vertex(hmap_scale*i, hmap_scale*j, height*hmap_scale);
}

void triangleFromHeightMap(int a, int b,
                           int c, int d,
                           int e, int f)
{
	Vertex v1 = vertexFromHeightMap(a,b);
	Vertex v2 = vertexFromHeightMap(c,d);
	Vertex v3 = vertexFromHeightMap(e,f);
    
	Vertex normal = Vector3::cross(v2-v1, v3-v1);
	glNormalV(normal);
    
	double maximum = max(heightmap[a][b], max(heightmap[c][d],
                                              heightmap[e][f]));
	
	double clr = (1-maximum/max_height)+0.2;
    
	if (clr<=0.01) {
		GLfloat blue[3] = {0,0,1};
		glMaterialfv(GL_FRONT, GL_AMBIENT, blue);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, blue);
	}
	else {
		GLfloat color_hm[3] = {clr,clr,clr};
		glMaterialfv(GL_FRONT, GL_DIFFUSE, color_hm);
		glMaterialfv(GL_FRONT, GL_AMBIENT, color_hm);
	}
	
	glBegin(GL_TRIANGLES);
	float TR = 5.0;
	glTexCoord2f(a/TR,b/TR); glVertexV(v1);
	glTexCoord2f(c/TR,d/TR); glVertexV(v2);
	glTexCoord2f(e/TR,f/TR); glVertexV(v3);
	glEnd();
}			  

void drawHeightMap(float offset_x, float offset_y, float offset_z)
{
	shader->unbind();
	terrainshader->bind();
	
	GLint terrainLoc = glGetUniformLocationARB(terrainshader->pid, "terrainTex");
	GLint terrainShadow = glGetUniformLocationARB(terrainshader->pid, "shadowToggle");

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, iceTexture);
	glUniform1i(terrainLoc, 1);
	glUniform1i(terrainShadow, shadowtoggle);

	errorcheck(__FILE__,__LINE__);
	glPushMatrix(); // PUSH HEIGHTMAP MATRIX BEFORE ROTATING
	glRotatef(89, 1, 0, 0);
	glTranslated(offset_x*(HMAP_SIZE-1)*hmap_scale, offset_y*(HMAP_SIZE-1)*hmap_scale, offset_z*(HMAP_SIZE-1)*hmap_scale);
	
	// TRANSLATION FOR THE SHADOW MAP
	glMatrixMode(GL_TEXTURE);
        glActiveTextureARB(GL_TEXTURE7);
        glPushMatrix();
        glRotatef(89, 1, 0, 0);
        glTranslated(offset_x*(HMAP_SIZE-1)*hmap_scale, offset_y*(HMAP_SIZE-1)*hmap_scale, offset_z*(HMAP_SIZE-1)*hmap_scale);

	for (int i=0; i<HMAP_SIZE-1; i++) {
		for (int j=0; j<HMAP_SIZE-1; j++) {
			triangleFromHeightMap(i,   j+1,
                                  i+1, j,
                                  i  , j);
			
			triangleFromHeightMap(i,   j+1  ,
                                  i+1  , j+1,
                                  i+1  , j);
		}
	}
	

	glPopMatrix(); // POP _TEXTURE_ MATRIX THAT WAS FOR ROTATION
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix(); // POP MATRIX THAT WAS FOR ROTATION
	
	terrainshader->unbind();
}

void setupMatrices(float position_x,float position_y,float position_z,float lookAt_x,float lookAt_y,float lookAt_z)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(FOV, RENDER_WIDTH/RENDER_HEIGHT, nearplane, farplane);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(position_x,position_y,position_z,lookAt_x,lookAt_y,lookAt_z,0,1,0);
}

void setTextureMatrix(void)
{
	static double modelView[16];
	static double projection[16];
	
	// This is matrix transform every coordinate x,y,z
	// x = x* 0.5 + 0.5 
	// y = y* 0.5 + 0.5 
	// z = z* 0.5 + 0.5 
	// Moving from unit cube [-1,1] to [0,1]  
	const GLdouble bias[16] = {	
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0};
	
	// Grab modelview and transformation matrices
	glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	
	glMatrixMode(GL_TEXTURE);
	glActiveTextureARB(GL_TEXTURE7);
	
	glLoadIdentity();	
	glLoadMatrixd(bias);
	
	// concatating all matrice into one.
	glMultMatrixd (projection);
	glMultMatrixd (modelView);
	
	// Go back to normal matrix mode
	glMatrixMode(GL_MODELVIEW);
}

// During translation, we also have to maintain the GL_TEXTURE8, used in the shadow shader
// to determine if a vertex is in the shadow.
void startTranslate(float x,float y,float z)
{
	glPushMatrix();
	glTranslatef(x,y,z);
	
	glMatrixMode(GL_TEXTURE);
	glActiveTextureARB(GL_TEXTURE7);
	glPushMatrix();
	glTranslatef(x,y,z);
}

void endTranslate()
{
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void drawObjects(void)
{
    /*
    // Ground
	glColor4f(0.0f,0.3f,0.0f,1);
	glBegin(GL_QUADS);
	glVertex3f(-350,2,-350);
	glVertex3f(-350,2, 150);
	glVertex3f( 150,2, 150);
	glVertex3f( 150,2,-350);
	glEnd();
    */
	glColor4f(0.9f,0.9f,0.9f,1);

	glPointSize(10);
	glColor3f(1.0, 0, 0);
	glBegin(GL_POINTS);
	glVertex3f(p_light[0], p_light[1], p_light[2]);
	glEnd();

	// Instead of calling glTranslatef, we need a custom function that also maintain the light matrix
	startTranslate(0,4,-16);
	glutSolidCube(4);
	endTranslate();
    

	if (!drawBear ) {
		glActiveTexture(GL_TEXTURE3);
		glBindTexture(GL_TEXTURE_2D, bearTexture);

		bearshader->bind();	
		GLint bearLoc = glGetUniformLocationARB(bearshader->pid, "bearTex");
		GLint bearShadow = glGetUniformLocationARB(bearshader->pid, "shadowToggle");

		glUniform1i(bearLoc, 3);
		glUniform1i(bearShadow, shadowtoggle);

		double bear_y = -20;
		glPushMatrix();
		glTranslatef(0, bear_y, -5);
		glScalef(1.5, 1.5, 1.5);
		glMatrixMode(GL_TEXTURE);
		glActiveTextureARB(GL_TEXTURE7);
		glPushMatrix();
		glTranslatef(0, bear_y, -5);
		glScalef(1.5, 1.5, 1.5);
		
		for (int i =0; i < nVerts/3; i ++)
			{
				glBegin(GL_TRIANGLES);
				glTexCoord3f(texcoords[9*i], texcoords[9*i+1], texcoords[9*i+2]);
				glVertex3f( vertices[9*i], vertices[9*i+1], vertices[9*i+2]);
				
				glTexCoord3f(texcoords[9*i+3], texcoords[9*i+4], texcoords[9*i+5]);
				glVertex3f(vertices[9*i+3], vertices[9*i+4], vertices[9*i+5]);
				
				glTexCoord3f(texcoords[9*i+6], texcoords[9*i+7], texcoords[9*i+8]);
				glVertex3f(vertices[9*i+6], vertices[9*i+7], vertices[9*i+8]);
				glEnd();
			}
		
		endTranslate();
		bearshader->unbind();
	}
}

void update(void)
{
	p_light[0] = light_mvnt * cos(glutGet(GLUT_ELAPSED_TIME)/1000.0);
	p_light[2] = light_mvnt * sin(glutGet(GLUT_ELAPSED_TIME)/1000.0);
}

void drawskybox() {
	// SKYBOX

	// Store the current matrix
	glPushMatrix();
 
	// Reset and transform the matrix.
	glLoadIdentity();
	camera->load(); 

	// Enable/Disable features
	glPushAttrib(GL_ENABLE_BIT);

	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glDisable(GL_BLEND);
 
	// Just in case we set all vertices to white.
	glColor4f(1,1,1,1);
 
	glDisable(GL_CULL_FACE);
	//	glCullFace(GL_FRONT);

	skyshader->bind();
	
	glEnable(GL_TEXTURE_2D);

	GLint texLoc = glGetUniformLocationARB(skyshader->pid, "skyTex");
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, skyTexture[0]);
	glUniform1i(texLoc, 1);

	float scale = 500.0;
	glMultMatrixd(Matrix4::createScaleMatrix(scale,scale,scale).getGLMatrix());

	float VALUE=0.5f;
	float ONE = 1.0f;
		
	glBindTexture(GL_TEXTURE_2D, skyTexture[5]);
	glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f(  VALUE, -VALUE, -VALUE );
        glTexCoord2f(ONE, 0); glVertex3f( -VALUE, -VALUE, -VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f( -VALUE,  VALUE, -VALUE );
        glTexCoord2f(0, ONE); glVertex3f(  VALUE,  VALUE, -VALUE );
	glEnd();
	
	glBindTexture(GL_TEXTURE_2D, skyTexture[1]);
	glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f(  VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, 0); glVertex3f(  VALUE, -VALUE, -VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f(  VALUE,  VALUE, -VALUE );
        glTexCoord2f(0, ONE); glVertex3f(  VALUE,  VALUE,  VALUE );
	glEnd();

	glBindTexture(GL_TEXTURE_2D, skyTexture[4]);

	glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f( -VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, 0); glVertex3f(  VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f(  VALUE,  VALUE,  VALUE );
        glTexCoord2f(0, ONE); glVertex3f( -VALUE,  VALUE,  VALUE );
	glEnd();

	glBindTexture(GL_TEXTURE_2D, skyTexture[0]);

       	glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f( -VALUE, -VALUE, -VALUE );
        glTexCoord2f(ONE, 0); glVertex3f( -VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f( -VALUE,  VALUE,  VALUE );
        glTexCoord2f(0, ONE); glVertex3f( -VALUE,  VALUE, -VALUE );
	glEnd();

	glBindTexture(GL_TEXTURE_2D, skyTexture[3]);

       	glBegin(GL_QUADS);
        glTexCoord2f(0, ONE); glVertex3f( -VALUE,  VALUE, -VALUE );
        glTexCoord2f(0, 0); glVertex3f( -VALUE,  VALUE,  VALUE );
        glTexCoord2f(ONE, 0); glVertex3f(  VALUE,  VALUE,  VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f(  VALUE,  VALUE, -VALUE );
	glEnd();

	glBindTexture(GL_TEXTURE_2D, skyTexture[2]);

       	glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f( -VALUE, -VALUE, -VALUE );
        glTexCoord2f(0, ONE); glVertex3f( -VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, ONE); glVertex3f(  VALUE, -VALUE,  VALUE );
        glTexCoord2f(ONE, 0); glVertex3f(  VALUE, -VALUE, -VALUE );
	glEnd();

	glPopAttrib();
	glPopMatrix();
}

void drawGun() {
	glPushMatrix();
	glLoadIdentity();
	
	gun_shader->bind();
	GLuint loc = glGetUniformLocationARB(gun_shader->pid, "gunTex");
	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, gunTexture);
	glUniform1i(loc, 4);

	glTranslatef(1.0f,-1.5f,0.0f);
	glColor3f(0,0,1);

	
        glBegin(GL_QUADS);
        glTexCoord2f(0,1); glVertex3f(0, 0, -3);
        glTexCoord2f(1,1); glVertex3f(1, 0, -3);
        glTexCoord2f(0,0); glVertex3f(0, 1, -3);
        glTexCoord2f(1,0); glVertex3f(1, 1, -3);
        glEnd();
	gun_shader->unbind();	

	glPopMatrix();
}

void display()
{


static int frames=0;
    
	glutWarpPointer(RENDER_WIDTH / 2.0, RENDER_HEIGHT / 2.0);

	if (shadowlight == -1)
		update();
    
	gettimeofday(&current_time, NULL);
	seconds  = current_time.tv_sec  - last_time.tv_sec;
	useconds = current_time.tv_usec - last_time.tv_usec;
    
	if (seconds >= 1.0 && frames > 0) {
		cout << "FPS: " << frames/(seconds+useconds/1000000.0) << endl;
		last_time=current_time;
		frames=0;
	}
    
	glClearColor(0,1,0,0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    
    //shadow mapping from here
    
	//First step: Render from the light POV to a FBO, store depth values only
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId);	//Rendering offscreen
	
	// In the case we render the shadowmap to a higher resolution, the viewport must be modified accordingly.
	glViewport(0,0,RENDER_WIDTH * SHADOW_MAP_RATIO,RENDER_HEIGHT* SHADOW_MAP_RATIO);
	
	// Clear previous frame values
	glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	
	//Disable color rendering, we only want to write to the Z-Buffer
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 
	
	setupMatrices(p_light[0],p_light[1],p_light[2],l_light[0],l_light[1],l_light[2]);

	//Save modelview/projection matrice into texture7, also add a bias
	setTextureMatrix();
	
	// Culling switching, rendering only backface, this is done to avoid self-shadowing
	glCullFace(GL_FRONT);

	drawObjects();

	//draw the height map
	for (int i=0; i<HMAP_SIZE-1; i++) {
		for (int j=0; j<HMAP_SIZE-1; j++) {
			glBegin(GL_TRIANGLES);
			glVertexV(vertexFromHeightMap(i, j+1));
			glVertexV(vertexFromHeightMap(i+1, j));
			glVertexV(vertexFromHeightMap(i,j));
			glEnd();
			
			glBegin(GL_TRIANGLES);
			glVertexV(vertexFromHeightMap(i, j+1));
			glVertexV(vertexFromHeightMap(i+1, j+1));
			glVertexV(vertexFromHeightMap(i+1,j));
			glEnd();
		}
	}

	// Now rendering from the camera POV, using the FBO to generate shadows
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
	
	glViewport(0,0,RENDER_WIDTH,RENDER_HEIGHT);
	
	//Enabling color write (previously disabled for light POV z-buffer rendering)
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 
	
	// Clear previous frame values
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	camera->load();
    
	//set the light
	GLfloat mat_specular[] = { 0.3, 0.3, 0.3, 1.0 };
	GLfloat mat_shininess[] = { 50.0 };
	GLfloat mat_ambient[] = { 0.7, 0.7, 0.7 };
	GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7 };
	static GLfloat light_position[] = { 0.0, 10.0, 0.0, 1.0 };
	GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 0.0 };
	glClearColor (0.0, 0.0, 0.0, 0.0);
	glShadeModel (GL_SMOOTH);
	
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
    
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    
	//Using the shadow shader
	if (shadowtoggle == 1 && drawBear) {
		shader->bind();
		//Using the fixed pipeline to render to the depthbuffer
		glActiveTextureARB(GL_TEXTURE7);
		glBindTexture(GL_TEXTURE_2D,depthTextureId);
		glActiveTextureARB(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D,depthTextureId);
	}
    
	glCullFace(GL_BACK);

	drawskybox();	

	drawObjects();

	drawHeightMap(-0.3,-0.3,0);

	drawGun();

	glutSwapBuffers();
	frames++;
}

void reshape(int w, int h)
{
	width = w;
	height = h;
	RENDER_WIDTH = w;
	RENDER_HEIGHT = h;
    
	glViewport(0, 0, w, h);                       
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	aspect_ratio = (double)w/(double)h;
	gluPerspective(FOV, aspect_ratio, nearplane, farplane);
	fmath.setFrustumSpecs(FOV, aspect_ratio, nearplane, farplane);
	glMatrixMode(GL_MODELVIEW); 
}

bool KeyDown[256];

void idle() 
{
	static double last=0;
	for (int i=0; i<256; i++) {
		if (KeyDown[i])
			camera->parse_key(i);
	}

	PROJECTION_X = floor(camera->position.v[0]/hmap_scale);
	PROJECTION_Z = floor(camera->position.v[2]/hmap_scale);    	

	int x = (int)(floor(camera->position.v[0]/hmap_scale)) % (HMAP_SIZE-1);
	int z = (int)(floor(camera->position.v[2]/hmap_scale)) % (HMAP_SIZE-1);

	while (x<0) x += HMAP_SIZE-1;
	while (z<0) z += HMAP_SIZE-1;

	
	double cam_y = -(heightmap[z][x]*hmap_scale-10*hmap_scale);

	if (abs(cam_y - last) > 0.001) {
		if (last!=0 && height_locked)
			camera->Y(cam_y-last);

		last = cam_y;
	}


	glutPostRedisplay();
} 

void keyboardUp(unsigned char key, int x, int y)
{
	KeyDown[key] = false;
}

void keyboard(unsigned char key, int a, int b)
{
	KeyDown[key] = true;
	
	if (key == 'b')
		drawBear = !drawBear;

	if (key=='+')
		hmap_scale *= 2.0;
	if (key=='-')
		hmap_scale /= 2.0;

	if (key == 'l')
		height_locked = !height_locked;

	if (key == 'n')
		heightmap = generate_heightmap(HMAP_SIZE, 1000.0);

	if (key == 'c') {
		for (int i=10; i<HMAP_SIZE-10; i++)
			for (int j=10; j<HMAP_SIZE-10; j++)
				heightmap[i][j] = -100;
	}
    
	if (key == 'p') {
		camera->position.print("CAMPOS");
		camera->lookAt.print("LOOKAT");
	}
    
	if (key == 't') {
		shadowtoggle *= -1;
	}
	
	if (key == 'r') {
		shadowlight *= -1;
	}
    
	if (key==27)
		exit(0);
	
	camera->setSpeed(hmap_scale);
}

void funcyMouse(int button, int state, int x, int y) {
	if (state == 0)
		drawBear = !drawBear;
}

void funcyMotion(int px, int py) {
	static int moves=0;
	static int last_x,last_y;
    
	if (moves==0) {
		last_x = px;
		last_y = py;
		moves=1;
		return;
	}
	
	int diff_x = last_x - px;
	int diff_y = last_y - py;
	double scale = 19000.0;
    
	if (diff_x != 0 || diff_y != 0) {
		camera->rotate_leftward(Radians((double)diff_x/scale));
		camera->look_upward(Radians((double)diff_y/scale));
	}
}

int main(int argc, char** argv)
{
	gettimeofday(&last_time, NULL);
	identity = new Matrix4();
	identity->identity();
	
	//                     POS              UP              LOOKAT
	camera = new FPSCamera(Vector3(32,20,0), Vector3(0,1,0), Vector3(2,0,-10));
    
	glutInit(&argc, argv);
	glutInitWindowSize(RENDER_WIDTH,RENDER_HEIGHT);
	glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
	glutCreateWindow("Final project in CSE167");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutKeyboardUpFunc(keyboardUp);
	glutIdleFunc(idle);
	glutPassiveMotionFunc(funcyMotion);
	glutMouseFunc(funcyMouse);
#ifdef FLYREV
	iceTexture = loadTexture("ice.ppm");
	gunTexture = loadTexture("gun.ppm");
	bearTexture = loadTexture("ice.ppm");
	skyTexture[0] = loadTexture("cube1_negative_x.ppm");
	skyTexture[1] = loadTexture("cube1_positive_x.ppm");
	skyTexture[2] = loadTexture("cube1_negative_y.ppm");
	skyTexture[3] = loadTexture("cube1_positive_y.ppm");
	skyTexture[4] = loadTexture("cube1_positive_z.ppm");
	skyTexture[5] = loadTexture("cube1_negative_z.ppm");
	shader = new Shader("shadowmapping.vert","shadowmapping.frag",true);
	terrainshader = new Shader("terrain.vert","terrain.frag",true);
	gun_shader = new Shader("gun.vert","gun.frag",true);
	skyshader = new Shader("skymap.vert","skymap.frag",true);
	bearshader = new Shader("bear.vert","bear.frag",true);
	ObjReader::readObj("bear.obj", nVerts, &vertices, &normals, &texcoords, nIndices, &indices);
#else
	iceTexture = loadTexture("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/ice.ppm");
	bearTexture = loadTexture("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/ice.ppm");
	skyTexture = loadTexture("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/ice.ppm");
	shader = new Shader("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/shadowmapping.vert","/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/shadowmapping.frag",true);
	terrainshader = new Shader("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/terrain.vert","/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/terrain.frag",true);
	bearshader = new Shader("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/bear.vert","/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/bear.frag",true);
	gunTexture = loadTexture("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/gun.ppm");

	ObjReader::readObj("/Users/c-yunhan/Documents/FinalProj/project1/project1/cse167-final/bear.obj", nVerts, &vertices, &normals, &texcoords, nIndices, &indices);
#endif
    
	if (iceTexture == 0)
		throw "Cannot load ice texture";
    
	initGL();
    
	glutFullScreen();
	glutSetCursor(GLUT_CURSOR_NONE);
	glutMainLoop();
	return 0;
}

